package com.connector.base.connection.impl;

import com.connector.base.connection.Connection;
import com.connector.base.connection.SessionContext;
import com.connector.base.connection.channel.NettyChannelSocketChannel;
import com.connector.base.connection.channel.SocketChannel;
import com.connector.base.exception.ConnectorException;
import com.connector.base.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Objects;

public final class NettyConnection implements Connection<Packet>, ChannelFutureListener
{
    private static final Logger log;
    private static final PooledByteBufAllocator ALLOCATOR;
    private static final int DEFAULT_TIMEOUT = 1000;
    private SessionContext context;
    private SocketChannel socketClient;
    private volatile byte status;
    private long lastReadTime;
    private long lastWriteTime;
    private int timeout;
    private String userId;
    private ByteBuf buf;

    public NettyConnection() {
        this.status = 0;
        this.timeout = 1000;
        this.buf = NettyConnection.ALLOCATOR.buffer();
    }

    @Override
    public void init(final SocketChannel socketClient) {
        this.socketClient = socketClient;
        this.context = new SessionContext();
        this.lastReadTime = System.currentTimeMillis();
        this.status = 1;
    }

    @Override
    public SessionContext getSessionContext() {
        return this.context;
    }

    @Override
    public void setSessionContext(final SessionContext context) {
        this.context = context;
    }

    @Override
    public ChannelFuture channelSend(final Packet packet) {
        return this.channelSend(packet, (ChannelFutureListener)null);
    }

    @Override
    public ChannelFuture channelSend(final Packet packet, final ChannelFutureListener listener) {
        final NettyChannelSocketChannel client = (NettyChannelSocketChannel)this.socketClient;
        final Channel thisChannel = client.getChannel();
        if (null != thisChannel && thisChannel.isActive() && this.isConnected()) {
            final ChannelFuture future = null;
            this.buf.clear();
            if (this.buf.readableBytes() > 0) {
                this.buf.resetReaderIndex();
            }
            if (this.buf.writableBytes() > 0) {
                this.buf.resetWriterIndex();
            }
            this.buf.retain();
            final TextWebSocketFrame frame = new TextWebSocketFrame(this.buf);
            this.buf.writeBytes(packet.getBody());
            thisChannel.writeAndFlush((Object)frame).addListener((GenericFutureListener)this);
            if (listener != null) {
                future.addListener((GenericFutureListener)listener);
            }
            if (thisChannel.isWritable()) {
                return future;
            }
            return future;
        }
        else {
            try {
                return this.channelClose();
            }
            catch (Exception e) {
                return null;
            }
        }
    }

    @Override
    public void sendEvent(final String eventName, final Object... data) {
        throw new ConnectorException("this method do not support the netty raw connection!");
    }

    @Override
    public ChannelFuture channelClose() {
        if (this.status == 2) {
            return null;
        }
        this.status = 2;
        final NettyChannelSocketChannel client = (NettyChannelSocketChannel)this.socketClient;
        if (Objects.isNull(client) || Objects.isNull(client.getChannel())) {
            return null;
        }
        return client.getChannel().close();
    }

    @Override
    public void disConnect() {
        final NettyChannelSocketChannel nettyChannel = (NettyChannelSocketChannel)this.socketClient;
        nettyChannel.getChannel().close();
    }

    @Override
    public String getId() {
        final NettyChannelSocketChannel client = (NettyChannelSocketChannel)this.socketClient;
        return client.getChannel().id().asShortText();
    }

    @Override
    public boolean isConnected() {
        return this.status == 1;
    }

    @Override
    public boolean isReadTimeout() {
        return System.currentTimeMillis() - this.lastReadTime > this.context.getHeartbeat() + this.timeout;
    }

    @Override
    public boolean isWriteTimeout() {
        return System.currentTimeMillis() - this.lastWriteTime > this.context.getHeartbeat() - this.timeout;
    }

    @Override
    public void updateLastReadTime() {
        this.lastReadTime = System.currentTimeMillis();
    }

    @Override
    public void updateLastWriteTime() {
        this.lastWriteTime = System.currentTimeMillis();
    }

    @Override
    public SocketChannel getSocketClient() {
        return this.socketClient;
    }

    public String getUserId() {
        return this.userId;
    }

    public void setUserId(final String Id) {
        this.userId = Id;
    }

    public void operationComplete(final ChannelFuture future) throws Exception {
        if (future.isSuccess()) {
            this.lastWriteTime = System.currentTimeMillis();
        }
        else {
            NettyConnection.log.error("connection send msg error", future.cause());
        }
    }

    @Override
    public String toString() {
        return "NettyConnection(context=" + this.context + ", socketClient=" + this.getSocketClient() + ", status=" + this.status + ", lastReadTime=" + this.lastReadTime + ", lastWriteTime=" + this.lastWriteTime + ", timeout=" + this.timeout + ", userId=" + this.getUserId() + ", buf=" + this.buf + ")";
    }

    static {
        log = LoggerFactory.getLogger((Class) NettyConnection.class);
        ALLOCATOR = PooledByteBufAllocator.DEFAULT;
    }
}
